/**
 * Friendly Interactive Recursion Educator (FIRE)
 * COMS 4115: Programming Languages & Translators
 * Prof. Aho
 * 
 * @author Brian Lu
 * @author Sahar Hasan
 * @author Steve Hehl
 * @author Tristan Naumann
 * @version 2009-05-12
 */
import org.antlr.runtime.*;
import java.util.*;

/**
 * WSFilter takes a FireLexer and replaces LEADING_WS tokens with
 * the appropriate INDENT and DEDENT tokens.
 */
public class WSFilter implements TokenSource {	
	FireLexer lexer;
	LinkedList<Token> tokens = new LinkedList<Token>();
	LinkedList<Token> inputs = new LinkedList<Token>();
	Stack<Integer> indents = new Stack<Integer>();
	Token last = null;

	/**
	 * Creates a new whitespace filter
	 * @param lexer The lexer to filter
	 */
	public WSFilter (FireLexer lexer) {
		this.lexer = lexer;
		indents.push(new Integer(0));
	}

	public String getSourceName() {
		return lexer.getSourceName();
	}
	
	/**
	 * Returns the next token that was lexed after doing some filtering work.
	 */
	public Token nextToken() {
		if ( tokens.size()>0 )
			return tokens.remove();		// if you have something to return
		try {
			fillBuffer();				// otherwise try to fill the buffer
		} catch (Exception e) {
			System.out.println(e.getMessage());
			System.exit(1);
		}
		return nextToken();
	}

	/**
	 * Fills up the output buffer by filtering the input buffer from the lexer.
	 * @throws Exception for empty file and indentation errors
	 */
	protected void fillBuffer() throws Exception {
		Token t = getToken();
//		boolean pass = false;	// pass through info for debugging
//		if (pass) { add(t); return; }
		
		// Process tokens after NEWLINEs and LEADING_WS and EOF
		if (t.getType() == FireLexer.NEWLINE) {
			add(t);
			t = getToken();
			process(t);
		} else if (t.getType() == FireLexer.LEADING_WS
				|| t.getType() == FireLexer.EOF) {
			process(t);
		} else {
			add(t);
		}
	}
	
	/**
	 * Processes a token's indentation level add appropriate indentation to
	 * the output stream.
	 * @param t the token to process
	 * @throws Exception for empty file and indentation errors
	 */
	protected void process(Token t) throws Exception {
		//if (t.getChannel() == FireLexer.HIDDEN)
		//	return;
		
		// get column info about token
		int col = t.getCharPositionInLine();
		if (t.getType() == FireLexer.EOF) {
			col = 0;
			if (last == null) throw new Exception("ERROR @1: Empty file!");
			if (last.getType() != FireLexer.NEWLINE
					&& last.getType() != FireParser.DEDENT
					&& last.getType() != FireParser.INDENT
					&& last.getType() != FireLexer.COMMENT) {
				Token nl = new CommonToken(FireLexer.NEWLINE,"\n");
				nl.setCharPositionInLine(t.getCharPositionInLine());
				nl.setLine(t.getLine());
				add(nl);
			}
		}
		else if (t.getType() == FireLexer.LEADING_WS)
			col = t.getText().length() + 1;
		
		//compare to last indent level
		int lastIndent = indents.peek().intValue();
		if (col > lastIndent) {
			indents.push(new Integer(col));
			
			// issue INDENT
			Token indent = new CommonToken(FireParser.INDENT,"");
			indent.setCharPositionInLine(t.getCharPositionInLine());
			indent.setLine(t.getLine());
			add(indent);
		} else if (col < lastIndent) {
			while (indents.peek() > col) {
				indents.pop();
				
				// issue DEDENT
				Token dedent = new CommonToken(FireParser.DEDENT,"");
				dedent.setCharPositionInLine(t.getCharPositionInLine());
				dedent.setLine(t.getLine());
				add(dedent);
			}
			if (col != indents.peek()) 
				throw new Exception("Indentation Error");
		}
		
		// add if you're not dealing with a LEADING_WS
		if (t.getType() != FireLexer.LEADING_WS) 
			add(t);
	}
	
	/**
	 * Stores token as last added and then puts it in the output stream.
	 * @param t Token to add to output buffer
	 */
	protected void add(Token t) {
		last = t;
		//if (t.getChannel() != FireLexer.HIDDEN)
		tokens.add(t);
	}
	
	/**
	 * Checks to see what the next token is
	 * @return the next token in the stream
	 */
	protected Token peekToken() {
		Token t = lexer.nextToken();
		inputs.add(t);
		return t;
	}
	
	/**
	 * Gets the next token from the input stream
	 * @return the next token from input stream
	 */
	protected Token getToken() {
		if (inputs.size() > 0)
			return inputs.remove();
		Token t = lexer.nextToken();
		if (t.getType()==FireLexer.LEADING_WS
				&& (peekToken()).getType()==FireLexer.EOF)
			return getToken();
		return t;
	}

}

